package ci.web.router;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Map;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONAware;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.util.TypeUtils;

import ci.web.HttpMethod;
import ci.web.annotaction.Inner;
import ci.web.annotaction.Limiter;
import ci.web.annotaction.Router;
import ci.web.codec.FileItem;
import ci.web.core.CiContext;
import ci.web.core.CiRequest;
import ci.web.core.CiResponse;
import ci.web.util.JxHelper;
import io.netty.handler.codec.http.HttpResponseStatus;

/**
 * 路由-脚本处理器
 * @author zhh
 */
public class CiHandler implements CiCall{

    protected final String path;
    protected final Executable method;
    protected final Limiter _lm;
    protected final String[] varNames;
    protected final Constructor<?> proxyCall;
    
    public CiHandler(String pkg, Executable method) {
        this.method = method;
        path = buildPath(method, pkg, className(), methodName());
        _lm = method.getAnnotation(Limiter.class);
        proxyCall = CiHandlerCallProxy.make(method);
        if(proxyCall==null){
        	varNames = JxHelper.getParamNames(method);
        }else{
        	varNames = null;
        }
    }
    
    protected String className(){
        return method.getDeclaringClass().getCanonicalName();
    }
    protected String methodName(){
        return method.getName();
    }
    
    @Override
    public int hashCode() {
        return path.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if(obj instanceof CiHandler){
            return obj==this;
        }
        return super.equals(obj);
    }
    
    @Override
    public String toString() {
        return String.format("{p:%s, j:%s.%s}", path, className(), methodName());
    }

    /**
     * 执行处理
     * @param ctx
     * @return
     * @throws Exception
     */
    @Override
    public boolean call(CiContext ctx) throws Exception {
        if(isAlowMethod(ctx.method())==false){
            ctx.out().setStatus(HttpResponseStatus.FORBIDDEN);
        }else if(proxyCall!=null){
        	proxyCall.newInstance(ctx);
        }else{
        	call_imp(ctx);
        }
        return true;
    }
    private static boolean isIndexName(String s){
		return s.length()==1 && s.charAt(0)>='0'||s.charAt(0)<='9';
	}
    protected void call_imp(CiContext ctx) throws Exception{
    	Class<?>[] types = method.getParameterTypes();
    	Object[] args = new Object[types.length];
    	JSONObject params = ctx.params();
    	Class<?> clazz;
    	String name;
    	for (int i = 0; i < types.length; i++) {
    		clazz = types[i];
    		name = varNames[i];
    		if(clazz==String.class){
				if(name.charAt(0)=='$'){
					if(name.equals("$uri")){
						args[i] = ctx.uri();
						continue;
					}else if(name.equals("$path")){
						args[i] = ctx.path();
						continue;
					}else if(name.equals("$body")){
						args[i] = new String(ctx.body());
						continue;
					}
				}
				args[i] = params.getString(name);
			}
    		else if(clazz==File.class){
    			if(isIndexName(name)){
    				int file_idx = Integer.parseInt(name);
    				args[i] = ctx.files().size()>file_idx ? ctx.files().get(file_idx).getFile():null;
    			}else{
    				FileItem item = ctx.getFile(name);
    				args[i] =  item!=null? item.getFile():null;
    			}
    		}
    		else if (clazz.isAssignableFrom(CiContext.class)) {//请求上下文做为参数
            	args[i] = ctx;
            }
			else if(clazz.isAssignableFrom(CiResponse.class)){
				args[i] = ctx.out();
			}else if(clazz.isAssignableFrom(CiRequest.class)){
				args[i] = ctx.in();
			}
			else if(clazz==byte[].class){//直接获取post-body字节数组
				args[i] = ctx.body();
			}else if(clazz==JSONObject.class){//直接使用 get/post/request作为参数
				args[i] = ctx.params();
				if(name.charAt(0)=='$'){
					if(name.equals("$post")){
						args[i] = ctx.post();
					}else if(name.equals("$get")){
						args[i] = ctx.get();
					}
				}
			}else if(clazz==JSONArray.class){//http-数组参数
				args[i] = params.getJSONArray(name);
			}else if(clazz==String[].class){//http-数组参数
				JSONArray arr = params.getJSONArray(name);
				args[i] = arr==null ? null:arr.toArray(new String[arr.size()]);
			}else{
				if(params.containsKey(name)){
					args[i] = TypeUtils.cast(params.get(name), clazz, ParserConfig.getGlobalInstance());
				}
			}
    	}
    	if(method instanceof Constructor<?>){
    		((Constructor<?>)method).newInstance(args);
    	}else{
    		boolean isStatic = Modifier.isStatic(method.getModifiers());
    		Method m = (Method)method;
    		Object ret = null;
    		if(isStatic){
				ret = m.invoke(null, args);
			}else{
				ret = m.invoke(m.getDeclaringClass().newInstance(), args);
			}
    		if(ret!=null && m.getReturnType()!=Void.class && ctx.isWroteBody()==false){
    			if(JSONAware.class.isAssignableFrom(m.getReturnType())){
    				ctx.send((JSONAware)ret);
    			}else if(byte[].class==m.getReturnType()){
    				ctx.send((byte[])ret);
    			}else if(File.class==m.getReturnType()){
    				ctx.send((File)ret);
    			}else{
    				ctx.send(String.valueOf(ret));
    			}
    		}
    	}
	}
	/**
     * 方法是否被允许
     * @param requestMethod
     * @return
     */
    protected boolean isAlowMethod(HttpMethod requestMethod) {
    	return _lm ==null || _lm.value()==requestMethod;
    }
    
    /**
     * 是否路径批量-匹配
     * @return
     */
    public boolean isPathMatch(){
    	Router r = method.getAnnotation(Router.class);
    	if(r!=null && r.value()!=null && r.value().endsWith("*")){
    		return true;
    	}
    	return false;
    }
    
    /**
     * 构建路由路径
     * @param method
     * @param pkg
     * @param className
     * @param methodName
     * @return
     */
    protected static String buildPath(Executable method, String pkg, String className, String methodName){
        Router r = method.getDeclaringClass().getAnnotation(Router.class);
        if(r != null){
            className = r.value().toLowerCase();
        }else{
            className = className.toLowerCase().replace(pkg.toLowerCase(), "").replaceAll("\\.", "/");
        }
        r = method.getAnnotation(Router.class);
        if(r != null){
            methodName = r.value();
            if(methodName.endsWith("*")){//如果函数路由最后以*结尾，认为是参数Handler
                methodName = methodName.substring(0, methodName.length()-1);
            }
        }
        methodName = methodName.toLowerCase();
        if((className.isEmpty() && methodName.isEmpty()) || (className.equals("/") && methodName.equals("/"))){
            return "/";
        }
        String p = className;
        if(methodName.isEmpty() || className.isEmpty()){
            p = className+methodName;
        }else if(methodName.charAt(0)!='/' && className.charAt(className.length()-1)!='/'){
            p = className+'/'+methodName;
        }else{
            p = className+methodName;
        }
        return p.charAt(0)=='/' ? p:'/'+p;
    }
    
    /**
     * 检查类是否适合ci路由
     * @param clazz
     * @return
     */
    public static boolean check(Class<?> clazz) {
    	if(clazz.getAnnotation(Inner.class)!=null){
    		return false;
    	}
        boolean canInstance = false;
        for(Constructor<?> constructor : clazz.getConstructors()){
            if(constructor.getParameterCount()==0){
                canInstance = true;
            }
        }
        return canInstance;
    }
    /**
     * 检查方法是否适合ci路由
     * @param method
     * @return
     */
    public static boolean check(Executable method) {
    	if(method.getAnnotation(Inner.class)!=null){
    		return false;
    	}
    	Class<?>[] types = method.getParameterTypes();
    	for(int i=0; i<types.length; i++){
            if(!isWebType(types[i])){
                return false;
            }
        }
        return true;
    }

    /**
     * 检测支持的参数
     * @param clazz
     * @return
     */
    protected static boolean isWebType(Class<?> clazz) {
        return clazz.isPrimitive() || 
        clazz==Boolean.class ||
        clazz==Byte.class ||
        clazz==Short.class ||
        clazz==Integer.class ||
        clazz==Long.class ||
        clazz==Float.class ||
        clazz==Double.class ||
        clazz==Character.class ||
        clazz==String.class ||
        clazz==byte[].class ||
        clazz==File.class ||
        clazz==String[].class||
        clazz==JSONArray.class||
        clazz==JSONObject.class||
        java.util.Date.class.isAssignableFrom(clazz) ||
        Map.class.isAssignableFrom(clazz) ||
        Collection.class.isAssignableFrom(clazz) ||
        CiContext.class==clazz ||
        CiRequest.class.isAssignableFrom(clazz)||
        CiResponse.class.isAssignableFrom(clazz)
        ;
    }


}
